﻿using System;
using Moq;
using NUnit.Framework;
using Tycho.Parser.Peg;

namespace Tycho.Parser.Tests.Peg {
    [TestFixture]
    public class NonTerminalSpecification {
        [Test]
        public void ReallyParseShouldReturnCompositeTermWithNamedRules () {
            const string termName = "multiply";
            NonTerminal nt = new NonTerminal (termName, null);
            nt.Rule = new SequenceRule (
                new NamedRule ("left", IdentifierProduction.CreateTerminal ()),
                new KeywordRule ("*"),
                new NamedRule ("right", IdentifierProduction.CreateTerminal ()));

            var source = "a * b";

            var result = nt.Parse (source, 0, new ParseContext ());

            var abTerm = result.Term as CompositeTerm;
            Assert.IsNotNull (abTerm);
            Assert.AreEqual (termName, abTerm.Name);

            var aTerm = abTerm.SubTerms ["left"] as IdentifierTerm;
            Assert.IsNotNull (aTerm);
            Assert.AreEqual ("a", aTerm.Name);

            var bTerm = abTerm.SubTerms ["right"] as IdentifierTerm;
            Assert.IsNotNull (bTerm);
            Assert.AreEqual ("b", bTerm.Name);

            Assert.AreEqual (0, abTerm.SourceInformation.Index);
            Assert.AreEqual (5, abTerm.SourceInformation.Length);
        }

        [Test]
        public void ReallyParseShouldSwapAssociationIfLeftAssociativeIsSet () {
            var termName = "plus";
            var nt = new NonTerminal (termName, null, new CompositeTermBuilder (null), new AssociationSwapper ("left", "right"));
            var choice = new ChoiceProduction (nt, IdentifierProduction.CreateTerminal ());
            nt.Rule = new SequenceRule (
                new NamedRule ("left", IdentifierProduction.CreateTerminal ()),
                new KeywordRule ("+"),
                new NamedRule ("right", choice));

            var source = "a + b + c";

            var result = choice.Parse (source, 0, new ParseContext ());

            var abcTerm = result.Term as CompositeTerm;

            Assert.IsNotNull (abcTerm);

            Assert.AreEqual (0, abcTerm.SourceInformation.Index);
            Assert.AreEqual (9, abcTerm.SourceInformation.Length);

            Assert.AreEqual (termName, abcTerm.Name);

            var abTerm = abcTerm.SubTerms ["left"] as CompositeTerm;
            Assert.IsNotNull (abTerm);
            Assert.AreEqual (termName, abTerm.Name);

            var aTerm = abTerm.SubTerms ["left"] as IdentifierTerm;
            Assert.IsNotNull (aTerm);
            Assert.AreEqual ("a", aTerm.Name);

            var bTerm = abTerm.SubTerms ["right"] as IdentifierTerm;
            Assert.IsNotNull (bTerm);
            Assert.AreEqual ("b", bTerm.Name);

            var cTerm = abcTerm.SubTerms ["right"] as IdentifierTerm;
            Assert.IsNotNull (cTerm);
            Assert.AreEqual ("c", cTerm.Name);
        }

        [Test]
        public void ShouldAddItemsToListThatAreInMultiplesSet () {
            var nt = new NonTerminal (
                "name",
                new SequenceRule (
                    new NamedRule ("a", IdentifierProduction.CreateTerminal ()),
                    new NamedRule ("a", IntegerProduction.CreateTerminal ())),
                new[] { "a", "b" });

            var source = "x 9";

            var result = nt.Parse (source, 0, new ParseContext ());
            Assert.IsNotNull (result);
            var compositeTerm = result.Term as CompositeTerm;
            Assert.IsNotNull (compositeTerm);
            Assert.AreEqual (2, compositeTerm.SubTerms.Count);
            var listTerm = compositeTerm.SubTerms ["a"] as ListTerm;
            Assert.IsNotNull (listTerm);
            Assert.AreEqual (2, listTerm.Terms.Count);
            Assert.AreEqual ("x", ((IdentifierTerm) listTerm.Terms [0]).Name);
            Assert.AreEqual (9, ((IntegerTerm) listTerm.Terms [1]).Value);

            Assert.AreEqual (0, compositeTerm.SourceInformation.Index);
            Assert.AreEqual (3, compositeTerm.SourceInformation.Length);

            listTerm = compositeTerm.SubTerms ["b"] as ListTerm;
            Assert.IsNotNull (listTerm);
            Assert.AreEqual (0, listTerm.Terms.Count);
        }

        [Test]
        public void MultiplesShouldDeferToTermBuilder () {
            var nt = new NonTerminal ();
            var multiples = new string[0];
            nt.TermBuilder = new CompositeTermBuilder (multiples);
            Assert.That (nt.Multiples, Is.EquivalentTo (multiples));

            nt.Multiples = multiples = new[] {"one"};
            Assert.That (nt.TermBuilder.Multiples, Is.EquivalentTo (multiples));
        }

        [Test]
        public void ShouldUseNonInfixCreatorWhenNotHasPrecedence () {
            var ruleParseResult = new RuleParseResult (0, null);
            var rule = new FakeRule (ruleParseResult);

            var compositeTermFactory = new Mock<ICompositeTermFactory> ();
            var createdCompositeTerm = new CompositeTerm ("name", null);
            compositeTermFactory
                .Setup (c => c.CreateCompositeTerm ("name", It.IsAny<SourceInformation> ()))
                .Returns (createdCompositeTerm);

            var nt = new NonTerminal ("name", rule);
            nt.CompositeTermFactory = compositeTermFactory.Object;
            nt.Precedence = null;

            ParseResult result = nt.Parse ("none".ToCharArray (), 0, new ParseContext (), "none",
                                   new ParseEnvironment ("filename"));

            Assert.That (result.Term, Is.SameAs (createdCompositeTerm));
        }

        [Test]
        public void ShouldUseInfixCreatorWhenHasPrecedence () {
            var ruleParseResult = new RuleParseResult(0, null);
            var rule = new FakeRule(ruleParseResult);

            var compositeTermFactory = new Mock<ICompositeTermFactory> ();
            var createdCompositeTerm = new CompositeTerm ("name", null);
            var precedence = new Mock<IPrecedence> ().Object;
            compositeTermFactory
                .Setup (c => c.CreateInfixCompositeTerm ("name", precedence, "first", "last", It.IsAny<SourceInformation> ()))
                .Returns (createdCompositeTerm);

            var nt = new NonTerminal ("name", rule);
            nt.CompositeTermFactory = compositeTermFactory.Object;
            nt.FirstCaptureName = "first";
            nt.LastCaptureName = "last";
            nt.Precedence = precedence;

            ParseResult result = nt.Parse ("none".ToCharArray (), 0, new ParseContext (), "none",
                                   new ParseEnvironment ("filename"));

            Assert.That (result.Term, Is.SameAs (createdCompositeTerm));
        }

        class FakeRule : IRule {
            private RuleParseResult result;

            public FakeRule (RuleParseResult result) {
                this.result = result;
            }

            public RuleParseResult Parse (char[] source, int index, ParseContext context, string sourceString, ParseEnvironment environment) {
                throw new NotImplementedException ();
            }

            public Yield Parse (char[] source, int index, ParseContext context, string sourceString, ParseEnvironment environment, Func<RuleParseResult, Yield> continuation) {
                return () => continuation (result);
            }

            public bool IsInfix {
                get { throw new NotImplementedException (); }
            }

            public IInfixInformation InfixInformation {
                get { throw new NotImplementedException (); }
            }

            public bool HasProduction {
                get { throw new NotImplementedException (); }
            }

            public IProduction Production { get; set; }
        }

        [Test]
        public void ProductionNameShouldBeSameAsTermName () {
            IProduction p = new NonTerminal ("some-name", null);
            Assert.That (p.Name, Is.EqualTo ("some-name"));
        }

        [Test]
        public void ShouldConstructNonTerminalFromProperties () {
            var nt = new NonTerminal ();

            nt.Name = "name";
            var rule = new SequenceRule ();
            nt.Rule = rule;
            var termFactory = new CompositeTermBuilder (new[] {"one", "two"});
            nt.TermBuilder = termFactory;

            Assert.That (nt.Name, Is.EqualTo ("name"));
            Assert.That (nt.Rule, Is.SameAs (rule));
            Assert.That (nt.TermBuilder, Is.SameAs (termFactory));
        }

        [Test]
        public void ShouldBeInfixIfNamedRulesAreFirstAndLast () {
            var nt = new NonTerminal ("name",
                                      new SequenceRule (new NamedRule ("a", null),
                                                        new KeywordRule ("*"),
                                                        new NamedRule ("b", null)));

            Assert.That (nt.IsInfix);
        }

        [Test]
        public void ShouldNotBeInfixIfNamedRuleOnlyFirst () {
            var nt = new NonTerminal ("name",
                                      new SequenceRule (new NamedRule ("a", null),
                                                        new KeywordRule ("*")));

            Assert.That (!nt.IsInfix);
        }

        [Test]
        public void ShouldNotBeInfixIfNamedRuleOnlyLast () {
            var nt = new NonTerminal ("name",
                                      new SequenceRule (new KeywordRule ("*"),
                                                        new NamedRule ("a", null)));

            Assert.That (!nt.IsInfix);
        }

        [Test]
        public void ShouldNotBeInfixIfRuleNotSequenceRule () {
            var nt = new NonTerminal ("name",
                                      new NamedRule ("a", null));

            Assert.That (!nt.IsInfix);
        }

        [Test]
        public void ShouldReturnInfixInformationForRule () {
            var rule = new Mock<IRule> ();
            var infixInformation = new Mock<IInfixInformation> ().Object;
            rule.SetupGet (r => r.InfixInformation).Returns (infixInformation);
            var nt = new NonTerminal ("name", rule.Object);

            Assert.That (nt.InfixInformation, Is.SameAs (infixInformation));
        }
    }
}